/*
* Copyright (c) 2010 Michael Collins
*
* This file is part of TerraFirma.
*
* TerraFirma is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TerraFirma is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with TerraFirma.  If not, see <http://www.gnu.org/licenses/>.
*/

//THIS IS THE CLIENT IMPLEMENTATION ONLY!!
#ifdef CLIENT

#include "network_thread.h"
#include <string.h>
#include <iostream>

extern engine::mpi MPI;

namespace engine
{

    network_thread::network_thread(boost::asio::io_service& io) :
        task_thread<local_network_msg>(name()),
        netpipe(msg_queue.read), service(io),
        sock(io, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0))
    {
        MPI.bind_netpipe(&netpipe);
        recv_seqs[0] = 0xFFFF; //Initialize to -1
        //Unlike the server, this doesn't start a transaction right away
        receive();
    }

    bool network_thread::connect(const char* hostname, short port)
    {
        //Make a structure to form an endpoint for the server
        boost::asio::ip::udp::resolver res(service);
        boost::asio::ip::udp::resolver::query query(boost::asio::ip::udp::v4(), hostname, "1337");
        boost::system::error_code ec;
        printf("Connecting to server %s:%d\n", hostname, port);
        boost::asio::ip::udp::resolver::iterator it = res.resolve(query, ec);
        if(ec)
        {
            std::cout << "Error in hostname resolution: " << ec.message() << std::endl;
            return false;
        }

        //So now it has an endpoint that should point to the server. Request a connection
        server = *it;
        net_header req(0,0);
        req.packet_type = net_header::CONNECTION_REQ;
        sock.send_to(boost::asio::buffer(&req, sizeof(req)), server);

        //Okay, now wait for a response
        net_header rsp;
        sock.receive_from(boost::asio::buffer(&rsp, sizeof(rsp)), server);
        if(rsp.packet_type == net_header::CONNECTION_ACPT)
        {
            //The connection was accepted! Welcome to the game network
            //Set the hostid given to us by the server
            context::Instance().hostid = rsp.dest_host;
            //recv_seqs[0] = rsp.seq;
            printf("Connection accepted! This host is host %d\n", rsp.dest_host);
            receive();
        } else {
            printf("Connection refused.\n");
            return false;
        }
        return true;
    }

    network_thread::~network_thread()
    {
        //dtor
    }

    void network_thread::wait()
    {
        boost::mutex dummy; //This is probably really stupid of me. Nobody really needs this mutex, we just want to be able to wake a condvar
        boost::unique_lock<boost::mutex> lock(dummy);

        while(msg_queue.isempty() && netpipe.isempty())
            msg_queue.read.wait(lock); //Wait for condition to be true
    }

    void network_thread::F(const boost::thread::id tid)
    {
        running = true;
        while(running)
        {
            wait(); //Blocks until a message comes in from another thread

            //Flush the queue
            while(!msg_queue.isempty())
            {
                process_message(msg_queue.front());
                msg_queue.pop();
            }

            //Send out all messages waiting to be sent to various hosts
            while(!netpipe.isempty())
            {
                send_message(netpipe.front());
                netpipe.pop();
            }
        } //while(running)
        service.stop();
    }

    void network_thread::process_message(const local_network_msg& msg)
    {
        if(msg.header.major_type() == BCAST_CLOSE)
        {
            running = false; //Stop the running of this thread
            //Send a disconnect message to the server
            disconnect_network_msg discon;
            //This is special because we don't want to continue receiving messages after sending this,
            //So don't call the async send complete handler
            sock.send_to(boost::asio::buffer(&discon, sizeof(disconnect_network_msg)), server);
        }


    }

    void network_thread::process_remote_message(const message_header& header_msg)
    {
        //For now, simply pass on message to the MPI where it will be directed where it needs to go
        //Check if it is a broadcast message
        #ifdef DEBUG
        std::cout << "got message " << header_msg << std::endl;
        #endif
        if(header_msg.major_type() >= BCAST_PAUSE)
            MPI.broadcast_local(&header_msg);
        else
            MPI.enqueue_local(&header_msg);
    }

    void network_thread::handle_receive_from(const boost::system::error_code& error, size_t bytes_received)
    {
        std::cout << "Received packet from host " << (int)data.cast().get_src_host() << " sequence " << data.header.seq << std::endl;
        if(!error && bytes_received > 0 && sender == server)
        {
            //We just got a packet from another host! Let's process it.

            //If this is a normal packet and is old (out of order) just discard it.
            if(data.header.packet_type == net_header::NORMAL)
            {
                if(IS_NEW_SEQ(data.header.seq, recv_seqs[data.cast().get_src_host()]))
                {
                    process_remote_message(data.cast());
                    recv_seqs[data.cast().get_src_host()] = data.header.seq;
                    receive();
                    return;
                } else
                {
                    receive();
                    return;
                }
            } else if(data.header.packet_type == net_header::ORDERED)
            {
                //Ordered packets need an ack back to the sender so the sender knows that the packet was
                //received
                net_header ack(data.cast().get_src_host(), data.header.seq);
                ack.packet_type = net_header::ACK;
                send(sender, &ack, sizeof(ack));
                if(IS_NEW_SEQ(data.header.seq, recv_seqs[data.cast().get_src_host()]))
                {
                    process_remote_message(data.cast());
                    recv_seqs[data.cast().get_src_host()] = data.header.seq;
                }
                return;
            }
            receive();
            return;

        }
        else //if(!error && bytes_received > 0)
            //Perhaps spit an error here, whatevs
            receive();
    }

    void network_thread::handle_send_to(const boost::system::error_code& error, size_t bytes_sent)
    {
        receive();
        printf("%ld bytes sent.\n", bytes_sent);
    }

    void network_thread::send_message(const inter_network_msg& m)
    {
        //We only need to send to the server
        //boost::asio::ip::udp::endpoint& ep = context::Instance().hosts.at(header.dest_host).endpoint;
        #ifdef DEBUG
        std::cout << m.cast() << "sent to server\n";
        #endif
        size_t size = m.cast().get_size() + sizeof(net_header); //Total size of the packet, including the header
        send(server, &m, size); //Send this packet out to the remote host
    }

}

#endif
